package xyz.labmem.engine

import cn.hutool.core.lang.Console
import cn.hutool.core.util.NumberUtil
import com.alibaba.fastjson.JSON
import javafx.application.Platform
import javafx.concurrent.Task
import org.openqa.selenium.ElementNotInteractableException
import org.openqa.selenium.JavascriptExecutor
import org.openqa.selenium.SessionNotCreatedException
import org.openqa.selenium.WebElement
import xyz.labmem.config.BrowserReptileTask
import xyz.labmem.controller.BrowserReptileController
import xyz.labmem.core.base.BaseController
import xyz.labmem.core.exception.LabRunException
import xyz.labmem.service.DriverOperating
import xyz.labmem.service.ReptileOperating
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.nio.charset.Charset
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.jvm.Throws


/**
 * 脚本引擎
 * @Author lty
 * @Date 2021/11/3 16:06
 */
class LabEngine(controller: BaseController) {

    private val base = controller as BrowserReptileController

    companion object {
        init {
            System.setProperty("nashorn.args", "--no-deprecation-warning")
        }

        /**
         * 判断括号正确
         */
        fun bracketsMatch(str: String): Boolean {
            val stack = Stack<Char>()
            for (i in str.indices) {
                when (str[i]) {
                    '{', '(', '[' -> stack.push(str[i])
                    '}', ')', ']' -> {
                        if (stack.isEmpty()) return false
                        if (!matchCheck(stack.pop(), str[i])) return false
                    }
                }
            }
            return stack.isEmpty()
        }

        fun matchCheck(strRight: Char, strLeft: Char): Boolean {
            when (strRight) {
                '{' -> if (strLeft == '}') return true
                '(' -> if (strLeft == ')') return true
                '[' -> if (strLeft == ']') return true
                else -> {
                }
            }
            return false
        }

    }

    private val provisionWord = arrayOf("fun", "main", "var", "if", "for", "param", "it", "_index", "true", "false")

    fun run(task: BrowserReptileTask): Task<DriverOperating> {
        return object : Task<DriverOperating>() {
            override fun call(): DriverOperating? {
                Platform.runLater { updateProgress(0.0, 1.0) }
                var frequency = 1   //执行次数
                var operating: DriverOperating? = null
                try {
                    //创建浏览器
                    operating = DriverOperating()
                    updateValue(operating)
                    //获取任务配置
                    task.config?.let {
                        if (it.frequency > 1)
                            frequency = it.frequency
                        if (it.outTime != null)
                            operating.outTime = it.outTime!!
                        if (it.threadSleep != null)
                            operating.outTime = it.threadSleep!!
                        if (it.uploadOutTime != null)
                            operating.outTime = it.uploadOutTime!!
                    }
                    Platform.runLater { updateProgress(0.15, 1.0) }
                    operating.apply {
                        var progress = 0.0
                        //打开页面
                        driver.get(task.url)
                        if (bracketsMatch(task.script)) {
                            val script = task.script.trim()
                            val zoneIndex = script.indexOf("{")
                            val zoneIndexEnd = script.lastIndexOf("}")
                            if (zoneIndex > -1 && zoneIndexEnd > -1) {
                                if (script.substring(0, zoneIndex).trim() == "main") {
                                    val zone = script.substring(zoneIndex + 1, zoneIndexEnd).trim()
                                    val br = BufferedReader(
                                        InputStreamReader(
                                            ByteArrayInputStream(zone.toByteArray(Charset.forName("utf8"))),
                                            Charset.forName("utf8")
                                        )
                                    )
                                    val lines = br.lines().toArray()
                                    //计算步骤进度
                                    if (lines.isNotEmpty()) {
                                        progress = 0.85.div(lines.size * frequency)
                                    }

                                    //临时入参
                                    val params = HashMap<String, Any>()
                                    params["dd"] = "123"
                                    while (frequency > 0) {

                                        val fieId = HashMap<String, Any>().apply { putAll(params) }
                                        var inc = false
                                        var eachTm = ArrayList<Any>()
                                        var cc = StringBuilder()
                                        lines.forEach {
                                            val sl = (it as String).trim()
                                            if (sl != "" && sl.indexOf("//") != 0) {
                                                if (inc && sl.indexOf("#if") == 0) {//end if
                                                    inc = false
                                                    zoneScript(cc.toString(), fieId, null, operating)
                                                } else if (inc && sl.indexOf("#each") == 0) {//end each
                                                    inc = false
                                                    eachTm.forEachIndexed { i, t ->
                                                        zoneScript(
                                                            cc.toString(),
                                                            fieId,
                                                            HashMap<String, Any>().apply {
                                                                put("it", t)
                                                                put("_index", i)
                                                            },
                                                            operating
                                                        )
                                                    }
                                                } else if (inc) {
                                                    cc.appendLine(it)
                                                } else if (sl.indexOf("if") == 0) {//if
                                                    val ifx = sl.substring(sl.indexOf("(") + 1, sl.lastIndexOf(")"))
                                                    //获取判断条件
                                                    if (ifx.contains("&&")) {

                                                    }

                                                    inc = true
                                                    cc = StringBuilder()
                                                } else if (sl.indexOf("var") == 0 || sl.indexOf("=") > 0) { //创建字段赋值
                                                    val d = sl.indexOf("=")
                                                    if (d > 0) {
                                                        val f = sl.substring(3, d).trim()
                                                        if (!provisionWord.contains(f) && !NumberUtil.isNumber(f)) {
                                                            val fl = sl.substring(d + 1).trim()
                                                            if (fl.indexOf("[") == 0 && fl.indexOf("]") == fl.length - 1) {
                                                                try {
                                                                    fieId[f] = JSON.parseArray(fl)
                                                                        .toJavaList(Any::class.java)
                                                                } catch (e: Exception) {
                                                                    Console.log("【$fl】赋值数组定义不正确")
                                                                }
                                                            } else if (fl.indexOf("(") > 0 && fl.indexOf(")") > fl.indexOf(
                                                                    "("
                                                                )
                                                            ) {
                                                                fieId[f] = function(fl, fieId, operating) ?: "null"
                                                            } else
                                                                fieId[f] = getValue(fl, fieId) ?: "null"
                                                        } else
                                                            throw LabRunException("变量名称不能使用【$it】")
                                                    }
                                                } else if (sl.contains(".each")) {//each
                                                    val eachIt = sl.substring(0, sl.indexOf(".each"))
                                                    if (fieId.contains(eachIt)) {
                                                        if (fieId[eachIt] is Collection<*>) {
                                                            eachTm =
                                                                ArrayList<Any>().apply { addAll(fieId[eachIt] as List<Any>) }
                                                            inc = true
                                                            cc = StringBuilder()
                                                        } else
                                                            throw LabRunException("【$sl】$eachIt 不是数组")
                                                    } else
                                                        throw LabRunException("【$sl】$eachIt 变量不存在")
                                                } else {
                                                    function(sl, fieId, operating)
                                                }
                                            }
                                            Platform.runLater { updateProgress(getProgress() + progress, 1.0) }
                                        }
                                        frequency--
                                        Thread.sleep(300)
                                    }
                                } else {
                                    throw LabRunException("缺少main")
                                }
                            } else
                                throw LabRunException("缺少括号")
                        } else
                            throw LabRunException("缺少括号")
                    }
                    Platform.runLater { updateProgress(1.0, 1.0) }
                    succeeded()
                    updateMessage(ReptileOperating.Status.SUCCESS.name)
                } catch (e: LabRunException) {
                    base.getTray().winMsgError("任务执行失败！", e.message ?: "【${task.name}】任务执行失败！详见日志")
                    updateMessage(ReptileOperating.Status.ERROR.name)
                    base.server.showLog("【${task.name}】任务执行有误：${e.message}")
                } catch (e: SessionNotCreatedException) {
                    e.printStackTrace()
                    base.getTray().winMsgError("Chrome创建失败！", "请检查驱动和Chrome版本")
                    updateMessage(ReptileOperating.Status.ERROR.name)
                    base.server.showLog("【${task.name}】Chrome创建失败：${e.message}")
                } catch (e: ElementNotInteractableException) {
                    base.getTray().winMsgError("元素未找到", "若页面加载慢请添加钩子确保页面正确！")
                    updateMessage(ReptileOperating.Status.ERROR.name)
                    base.server.showLog(
                        "【${task.name}】任务执行元素未找到,若页面加载慢请添加钩子确保页面正确再进行！error:" +
                                e.message!!.substring(
                                    e.message!!.lastIndexOf("Element:"),
                                    e.message!!.lastIndexOf("Session ID:")
                                )
                    )
                } catch (e: Exception) {
                    e.printStackTrace()
                    base.getTray().winMsgError("任务执行错误！", e.message ?: "启动失败！详见日志")
                    updateMessage(ReptileOperating.Status.ERROR.name)
                    base.server.showLog("【${task.name}】任务执行报错：${e.message}")
                } finally {
                    operating?.driverEnd()
                }

                return operating
            }
        }
    }

    @Throws(LabRunException::class)
    fun zoneScript(
        zone: String,
        params: HashMap<String, Any>,
        zoneParams: HashMap<String, Any>?,
        operating: DriverOperating
    ) {
        val br = BufferedReader(
            InputStreamReader(
                ByteArrayInputStream(zone.toByteArray(Charset.forName("utf8"))),
                Charset.forName("utf8")
            )
        )
        try {
            val fieId = HashMap<String, Any>().apply {
                putAll(params)
                zoneParams?.let { putAll(it) }
            }
            br.forEachLine {
                val sl = it.trim()
                if (sl.indexOf("var") == 0) { //赋值
                    val d = sl.indexOf("=")
                    if (d > 0) {
                        val f = sl.substring(3, d).trim()
                        if (!provisionWord.contains(f) && !NumberUtil.isNumber(f)) {
                            val fl = sl.substring(d + 1).trim()
                            if (fl.indexOf("[") == 0 && fl.indexOf("]") == fl.length - 1) {
                                try {
                                    fieId[f] = JSON.parseArray(fl)
                                        .toJavaList(Any::class.java)
                                } catch (e: Exception) {
                                    Console.log("【$fl】赋值数组定义不正确")
                                }
                            } else if (fl.indexOf("(") > 0 && fl.indexOf(")") > fl.indexOf("(")) {
                                function(fl, fieId, operating)
                            } else
                                fieId[f] = getValue(fl, fieId) ?: "null"
                        } else
                            throw LabRunException("变量名称不能使用【$it】")
                    }
                } else if (sl.indexOf("=") > 0) {
                    val d = sl.indexOf("=")
                    if (d > 0) {
                        val f = sl.substring(0, d).trim()
                        if (!provisionWord.contains(f) && !NumberUtil.isNumber(f)) {
                            val fl = sl.substring(d + 1).trim()
                            if (fl.indexOf("[") == 0 && fl.indexOf("]") == fl.length - 1) {
                                try {
                                    params[f] = JSON.parseArray(fl)
                                        .toJavaList(Any::class.java)
                                } catch (e: Exception) {
                                    Console.log("【$fl】赋值数组定义不正确")
                                }
                            } else
                                params[f] = getValue(fl, fieId) ?: "null"
                        } else
                            throw LabRunException("变量名称不能使用【$it】")
                    }
                } else {
                    function(sl, fieId, operating)
                }
            }
        } catch (e: Exception) {
            throw LabRunException("脚本错误 ${e.message}", e)
        }

    }

    fun getValue(s: String, p: HashMap<String, Any>): Any? {
        return if (s == "null" || s == "") {
            null
        } else if (NumberUtil.isNumber(s))
            s
        else if (s[0] == '"' && s[s.length - 1] == '"') {
            s.substring(1, s.length - 1)
        } else if (s[0] == '\'' && s[s.length - 1] == '\'') {
            s.substring(1, s.length - 1)
        } else
            p[s]
    }

    @Throws(LabRunException::class)
    fun function(str: String, params: HashMap<String, Any>, operating: DriverOperating): Any? {
        val funs = str.trim().split(".")

//        val f = str.substring(str.indexOf("(") + 1, str.lastIndexOf(")"))
//        val ps = f.split(",")
//        val param = LinkedList<Any?>()
//        ps.forEach { p ->
//            param.add(getValue(p, params))
//        }
        var pd: Any? = null
        var parentMethod: String? = null
        funs.forEach {
            //方法操作
            if (it.contains("(") && it.contains(")")) {
                val pa = it.substring(it.indexOf("(") + 1, it.lastIndexOf(")"))
                when (it.substring(0, it.indexOf("("))) {
                    "click" -> {
                        if (pd != null && pd is WebElement)
                            (pd as WebElement).click()
                        else
                            throw LabRunException("脚本【$str -> $it】无法定位需要click的Ele")
                    }
                    "console" -> {
                       Console.log("脚本Console : ${getValue(pa, params)}")
                    }
                    "selectEle" -> {
                        parentMethod = "selectEle"
                    }
                    "hook" -> {
                        parentMethod = "hook"
                    }
                    "backView" -> {
                        operating.driver.navigate().back()
                    }
                    "refresh" -> {
                        operating.driver.navigate().refresh()
                    }
                    "to" -> {
                        val path = getValue(pa, params)
                        if (path != null && (path.toString().indexOf("http://") == 0 || path.toString()
                                .indexOf("https://") == 0)
                        )
                            operating.driver.navigate().to(path.toString())
                        else
                            throw LabRunException("脚本【$str -> $it】 网址参数不正确")
                    }
                    "input" -> {
                        if (pd != null && pd is WebElement) {
                            val value = getValue(pa, params)
                            (pd as WebElement).sendKeys(value.toString())
                        } else
                            throw LabRunException("脚本【$str -> $it】无法定位需要input的Ele")
                    }
                    "getText" -> {
                        if (pd != null && pd is WebElement) {
                            pd = operating.getElementText((pd as WebElement), str)
                        } else
                            throw LabRunException("脚本【$str -> $it】无法定位需要获取Text的Ele")
                    }
                    "getAttr" -> {
                        if (pd != null && pd is WebElement) {
                            val value = getValue(pa, params)
                            if (value != null) {
                                pd = operating.getElementAttribute((pd as WebElement), value.toString(), str)
                            } else
                                throw LabRunException("脚本【$str -> $it】需要传入元素Attribute的key")
                        } else
                            throw LabRunException("脚本【$str -> $it】无法定位需要获取Attr的Ele")
                    }
                    "saveValue" -> {
                        val value = getValue(pa, params)
                        //TODO
                    }
                    "sleep" -> {
                        val value = getValue(pa, params)
                        try {
                            Thread.sleep(value.toString().toLong())
                        } catch (e: Exception) {
                            throw LabRunException("脚本【$str -> $it】请传入正确的参数Long，单位毫秒")
                        }
                    }
                    "savePhoto" -> {
                        if (pd != null && pd is WebElement) {
                            val path = getValue(pa, params)
                            if (path != null) {
                                operating.savePhoto((pd as WebElement), path.toString())
                            } else
                                throw LabRunException("脚本【$str -> $it】请传入保存图片的路径，例如 C:\\测试.png")
                        } else
                            throw LabRunException("脚本【$str -> $it】无法定位图片的Ele")
                    }
                    "jsRun" -> {
                        val js = getValue(pa, params)
                        if (js != null) {
                            (operating.driver as JavascriptExecutor).executeScript(js.toString())
                        } else
                            throw LabRunException("脚本【$str -> $it】请传入在页面执行的js")
                    }
                    "submit" -> {
                        if (pd != null && pd is WebElement) {
                            (pd as WebElement).submit()
                        } else
                            throw LabRunException("脚本【$str -> $it】无法定位submit的Ele")
                    }
                    "byId" -> {
                        if (pd != null && pd is WebElement) {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val id = getValue(pa, params)
                                    if (id != null) {
                                        pd = operating.getElementById(id.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val id = getValue(pa, params)
                                    if (id != null) {
                                        operating.loadingFindFlagById(id.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        } else {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val id = getValue(pa, params)
                                    if (id != null) {
                                        pd = operating.getElementById(id.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val id = getValue(pa, params)
                                    if (id != null) {
                                        operating.loadingFindFlagById(id.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        }
                    }
                    "byClass" -> {
                        if (pd != null && pd is WebElement) {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val clazz = getValue(pa, params)
                                    if (clazz != null) {
                                        pd = operating.getElementByClass(clazz.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val clazz = getValue(pa, params)
                                    if (clazz != null) {
                                        operating.loadingFindFlagByClass(clazz.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        } else {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val clazz = getValue(pa, params)
                                    if (clazz != null) {
                                        pd = operating.getElementByClass(clazz.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val clazz = getValue(pa, params)
                                    if (clazz != null) {
                                        operating.loadingFindFlagByClass(clazz.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        }
                    }
                    "byName" -> {
                        if (pd != null && pd is WebElement) {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val name = getValue(pa, params)
                                    if (name != null) {
                                        pd = operating.getElementByName(name.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        } else {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val name = getValue(pa, params)
                                    if (name != null) {
                                        pd = operating.getElementByName(name.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        }
                    }
                    "byTag" -> {
                        if (pd != null && pd is WebElement) {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val tag = getValue(pa, params)
                                    if (tag != null) {
                                        pd = operating.getElementByTag(tag.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        } else {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val tag = getValue(pa, params)
                                    if (tag != null) {
                                        pd = operating.getElementByTag(tag.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        }
                    }
                    "byXpath" -> {
                        if (pd != null && pd is WebElement) {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val xpath = getValue(pa, params)
                                    if (xpath != null) {
                                        pd = operating.getElementByXpath(xpath.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val xpath = getValue(pa, params)
                                    if (xpath != null) {
                                        operating.loadingFindFlagByXpath(xpath.toString(), (pd as WebElement))
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        } else {
                            when (parentMethod) {
                                "selectEle" -> {
                                    val xpath = getValue(pa, params)
                                    if (xpath != null) {
                                        pd = operating.getElementByXpath(xpath.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                "hook" -> {
                                    val xpath = getValue(pa, params)
                                    if (xpath != null) {
                                        operating.loadingFindFlagByXpath(xpath.toString(), null)
                                    } else
                                        throw LabRunException("脚本【$str -> $it】 参数不正确")
                                }
                                else -> throw LabRunException("脚本【$str -> $it】未知操作")
                            }
                        }
                    }
                    else -> throw LabRunException("脚本【$str】 未知方法 $it")
                }
            } else {
                pd = getValue(it, params)
            }
        }
        return pd
    }

}